package com.sxj.corejava.code11_io.字节流;

import java.io.*;

/**
 * @author 石小俊
 * @date 2025年02月21日
 * 文件工具类
 */
public class FileUtil {

    public static void main(String[] args) throws IOException {
//        copyFile("E:\\test\\a\\Hello.txt", "E:\\test\\b\\Hello.txt");
//        copyFile(new File("E:\\test\\a\\user.txt"), new File("E:\\test\\b\\user.txt"));
//        copyDirectory("E:\\test\\a", "E:\\test\\b");
//        recursiveCopy("E:\\test\\a", "E:\\test\\c");
//        splitFile("E:\\test\\a\\ideaIU-2021.3.3.exe", 100 * 1024 * 1024);
//        mergeFile(new File("E:\\test\\a\\ideaIU-2021.3.3.exe_1"));
//        encoded(new File("a.txt"), 123456);
        decoded(new File("a.txt.sec"), 123456);
    }

    /**
     * 拷贝文件,将源文件拷贝一份到目标文件中
     * @param src    源文件
     * @param target 目标文件
     */
    public static void copyFile(File src, File target) throws IOException {
        try (
                InputStream in = new FileInputStream(src);
                OutputStream out = new FileOutputStream(target);
        ) {
            byte[] b = new byte[1024];
            int i = -1;
            while ((i = in.read(b)) != -1) {
                out.write(b, 0, i);
            }
            System.out.println("提示:文件复制成功");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 拷贝文件,将源文件拷贝一份到目标文件中
     * @param src    源文件路径
     * @param target 目标文件路径
     */
    public static void copyFile(String src, String target) throws IOException {
        copyFile(new File(src), new File(target));
    }

    /**
     * 拷贝目录,将源目录中所有文件拷贝到目标目录中
     * @param src    源目录
     * @param target 目标目录
     */
    public static void copyDirectory(String src, String target) throws IOException {
        File srcFile = new File(src);
        // 获取该目录中所有的资源
        File[] files = srcFile.listFiles();

        // 创建目标目录
        File targetFile = new File(target);
        targetFile.mkdirs();

        // 遍历所有的资源
        for (File file : files) {
            // 如果当前资源是一个文件
            if (file.isFile()) {
                // 将则该文件拷贝到目标目录中
                // 调用工具类中提供的拷贝文件方法,将当前文件拷贝到目标目录中,且文件名同名
                // 此时目标文件的值为:目标目录名+路径分隔符+文件名
                copyFile(file, new File(target + File.separator + file.getName()));
            }
        }
    }

    /**
     * 递归拷贝,将一个目录中的文件与子目录递归拷贝到一个指定的目录中
     * @param src    源目录
     * @param target 目标目录
     */
    public static void recursiveCopy(String src, String target) throws IOException {
        File srcFile = new File(src);
        // 获取该目录中所有的资源
        File[] files = srcFile.listFiles();

        // 创建目标目录
        File targetFile = new File(target);
        targetFile.mkdirs();

        for (File file : files) {
            // 如果是文件
            if (file.isFile()) {
                // 将则该文件拷贝到目标目录中
                // 调用工具类中提供的拷贝文件方法,将当前文件拷贝到目标目录中,且文件名同名
                // 此时目标文件的值为:目标目录名+路径分隔符+文件名
//                copyFile(file, new File(target + File.separator + file.getName()));
                // 也可以通过构造函数指定目标目录名与文件名
                copyFile(file, new File(target, file.getName()));
            } else {
                // 如果是目录,递归操作
                // 将当前子目录中的所有资源拷贝到目标目录的子目录中
                // 此时目标目录的子目录名与源目录的子目录名一致
                // 因此,目标目录的子目录最终路径为:目标目录+分隔符+当前目录名
                recursiveCopy(file.getAbsolutePath(), target + File.separator + file.getName());
            }
        }
    }

    /**
     * 拆分文件,将一个文件按照指定大小拆分成多个文件,每一个文件名字为:原名字_index,index从1开始,每次自增1
     * 提示:一个输入流,多个输出流,在输出时,每循环一次,输出一个子文件
     * @param path   源文件
     * @param length 分隔的文件长度,单位:字节
     */
    public static void splitFile(String path, int length) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream(path);
            // 设置每次读取的字节数,即为分隔的文件长度
            byte[] b = new byte[length];
            int i = -1;
            // 定义文件下标
            int index = 1;
            while ((i = in.read(b)) != -1) {
                // 每读取一次,生成一个输出流,写入一个文件
                out = new FileOutputStream(path + "_" + index);
                out.write(b, 0, i);
                // 由于此处存在多个输出流,每循环一次,创建一个新的输出流
                // 因此,在第二次循环前,需要将第一次创建的输出流关闭
                out.close();
                System.out.println("提示:文件" + path + "_" + index + "已分隔完成");
                index++;
            }
            System.out.println("提示:文件全部分隔完成");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } finally {
            // 关闭输入流
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    throw e;
                }
            }
        }
    }

    /**
     * 合并文件,合并文件,将之前拆分的文件合并为一个文件,将子文件的_index部分删除作为文件名
     * 提示:一个输出流,多个输入流
     * 由于文件过多,参数只指定第一个文件的名字,其他文件名格式与第一个一致
     * @param firstFile 第一个文件名,格式为:文件名_index
     */
    public static void mergeFile(File firstFile) throws IOException {
        try {
            // 获取文件名部分的值
            String path = firstFile.getAbsolutePath();
            // 截取字符串,将文件名后面的内容截取掉
            path = path.substring(0, path.lastIndexOf("_"));
            // 获取到文件名后,指定一个输出流,用于接收合并后的文件
            OutputStream out = new FileOutputStream(path);
            // 定义合并前文件的下标
            int index = 1;
            File file = null;
            // 每个文件合并一次
            // 当文件名_index的文件存在,则需要合并,直到文件不存在为止
            while ((file = new File(path + "_" + index++)).exists()) {
                // 定义多个输入流
                FileInputStream in = new FileInputStream(file);
                // 拷贝流
                byte[] b = new byte[1024 * 1024];
                int i = -1;
                while ((i = in.read(b)) != -1) {
                    out.write(b, 0, i);
                }
                // 每循环一次,创建一个新的输入流
                // 需要在第二次循环前,关闭第一次的输入流
                in.close();
            }
            System.out.println("提示:" + path + "文件合并成功");
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 文件加密,读取文件中的每一个字节,根据指定的密码对其进行加密,加密后的文件添加.sec后缀
     * 例如:a.txt --> a.txt.sec
     * 加密通过异或操作实现
     * @param file
     * @param password
     */
    public static void encoded(File file, int password) throws IOException {
        try (
                FileInputStream in = new FileInputStream(file);
                FileOutputStream out = new FileOutputStream(file.getAbsolutePath() + ".sec");
        ) {
            int i = -1;
            while ((i = in.read()) != -1) {
                out.write(i ^ password);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 文件解密,读取加密后的文件中的每一个字节,根据指定的密码对其进行解密,解密后的文件去除.sec后缀
     * 例如:a.txt.sec --> a.txt
     * 解密通过异或操作实现
     * @param file
     * @param password
     */
    public static void decoded(File file, int password) throws IOException {
        try (
                FileInputStream in = new FileInputStream(file);
                FileOutputStream out = new FileOutputStream(file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(".")));
        ) {
            int i = -1;
            while ((i = in.read()) != -1) {
                out.write(i ^ password);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw e;
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }
}
